Skip to content

Make phone pairing survive real networks - #126

Merged
badcuban merged 8 commits into
mainfrom
fix/pairing-lan-gaps
Aug 8, 2026
Merged

Make phone pairing survive real networks#126
badcuban merged 8 commits into
mainfrom
fix/pairing-lan-gaps

Conversation

@badcuban

@badcuban badcuban commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes all six majors from the pairing end-to-end audit (preview-6). Root cause class: phones pair over plain http (insecure context) where secure-context APIs are undefined — all prior testing on localhost could never see it.

  • P-008/P-004: real randomUUID fallback (RFC 4122 v4 via getRandomValues) in one shared helper — the old fallback re-threw the same TypeError. Un-crashes phone arrival.
  • Sweep: 8 clipboard call sites unified behind one helper with execCommand fallback; getUserMedia error names insecure origins; nothing else secure-context-gated in the web bundle (verified list in commit).
  • P-001: serve mode's printed pairing URL/QR uses a reachable interface when bound beyond loopback (display-only change; binding untouched; desktop flow verified already correct).
  • P-002/3: pairing reveal dialog shows labelled setup link + labelled code + QR with working copy buttons on LAN origins.
  • P-005: Add computer modal hung on an unbounded getConfig pinned to a swapped-away socket; now a bounded reconnect-retrying read (10s/30s), remote auth fetches timeboxed.
  • P-006/7: not a data bug — the Devices list rendered nothing while loading (pixel-identical to empty, revoke disabled by vacuous [].every). Now shows a loading state; the audit's own screenshots prove listing/revoke work once loaded.

Tests: new uuid + clipboard suites, startupAccess host cases, wsRpcClient retry path, SettingsPanels loading-state case. Gates green; web 2077 unit, SettingsPanels 36, SourceControlPanel 48, shared 260.

Sol re-walks the full phone journey on this branch before merge (checklist in the build report: real LAN arrival with composer, clipboard on http, serve QR shows LAN address, Add computer resolves, revoked-phone state).

A phone pairs over plain http on the LAN, which is an insecure browser
context, and the app assumed secure-context APIs throughout. Every
direct crypto.randomUUID call threw there (the existing fallback was a
decoy that re-threw the same error), leaving a freshly paired phone in
an empty shell. UUIDs now come from one shared helper that falls back
to RFC 4122 v4 over getRandomValues; the eight ad-hoc clipboard call
sites share one helper with an execCommand fallback; getUserMedia's
error names the insecure origin when that is the cause.

The pairing surfaces got the same treatment end to end: serve mode
prints a pairing URL and QR on a reachable interface instead of
localhost (display only; binding is unchanged); the reveal dialog shows
a labelled setup link, labelled pairing code, QR, and working copy
buttons on LAN origins; Add computer no longer hangs on Adding forever
when its unbounded getConfig await outlives a socket swap (bounded
reconnect-retrying read, plus timeboxed remote auth fetches); and the
desktop Devices list says it is loading instead of rendering an empty
region that reads as no devices while revoke sits disabled.
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
threadlines Skipped Skipped Aug 8, 2026 4:55am

Request Review

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Aug 7, 2026
On the nightly channel the update chip compacted the target to its base
triple, so "v0.3.2 available" could mean the stable or any of the
day's nightlies. The release workflow's run counter is monotonic across
days, so a same-base nightly target now labels as just ".222" (shorter
than the old ambiguous label), a cross-base nightly as
"v0.3.3-nightly", and stables keep the exact triple. Tooltips always
carry the full version string.
@badcuban

badcuban commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Added a scoped second commit: the sidebar update chip now labels same-base nightly targets by run number (".222") instead of the ambiguous compacted base, with full versions in tooltips. 53/53 logic tests.

The chip fits about eleven characters, so v0.3.3-nightly would
ellipsize into exactly the part that mattered. The feed only serves the
selected channel, so the chip drops the suffix (v0.3.3 on the nightly
track can only be a nightly) while the popout row and tooltip keep the
explicit channel.
The re-walk's remaining pairing gaps, root-caused:

- The empty no-composer shell a paired phone landed on was a cold-start
  hydration lie: the app rendered "No projects yet" and the no-active-
  thread state in the ~1s window before the first shell snapshot
  arrived, on every entry path (the audit's owner-context anomaly was
  the same window). The directly-paired path now shows a loading state
  until bootstrap completes, exactly as the hosted path already did;
  the sidebar's project list gets the same gate.

- Revoking a device only rewrote persistence, so an already-connected
  phone kept streaming until it happened to reconnect. The websocket
  now races a revocation watcher (subscribe-then-read, so no missed
  signals; a failed watcher parks rather than killing a trusted
  socket). Client-side, a rejected reconnect is indistinguishable from
  a network drop in the browser, so after a disconnect the app asks
  the session endpoint: an explicit not-authenticated answer stops the
  retries and shows "Access removed. This device was disconnected from
  the computer. Pair again to reconnect."; any probe failure keeps
  reconnecting, so a network blink can never lock a device out.

- The manual pairing path on a direct LAN origin is the token form by
  design ("Add computer" belongs to the hosted app, which has no local
  backend); the form's happy and rejected paths gained the coverage
  they were missing.
@badcuban

badcuban commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Re-walk residuals fixed on this branch: the phone's empty-shell arrival was a cold-start hydration gap (empty states rendered before the first snapshot; now gated behind a loading state on every entry path), revoked devices now lose their live socket immediately and see a clear Access removed state (with an HTTP probe distinguishing revocation from network blips — no false lockouts), and the LAN manual path was confirmed works-as-designed with new token-form coverage. Final targeted sol re-walk runs before merge.

The boot log's pairing URL shared its derivation with the browser-open
target, so a server bound to every interface still advertised
localhost — a pairing link only this machine could open. The advertised
URL now follows the same rule headless serve uses: explicit hosts
verbatim, wildcard binds resolve a reachable interface, and the desktop
shell keeps localhost since its device URLs come from the Devices
dialog.
The first external IPv4 on a developer machine is often a WSL, Hyper-V,
Docker, or VPN adapter whose subnet no phone can reach, so the pairing
URL advertised a dead address. Virtual-looking interface names are
deprioritized rather than excluded; a machine with only virtual
adapters still advertises its best candidate.
@badcuban
badcuban merged commit 2fe2c86 into main Aug 8, 2026
15 checks passed
@badcuban
badcuban deleted the fix/pairing-lan-gaps branch August 8, 2026 05:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant